package com.gao.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gao.entity.Order;
import com.gao.entity.OrderItem;
import com.gao.entity.OrderItemExample;
import com.gao.entity.Product;
import com.gao.exception.OrderException;
import com.gao.mapper.OrderItemMapper;
import com.gao.mapper.OrderMapper;
import com.gao.service.OrderService;
import com.gao.service.ProductService;
import com.gao.util.ResultCode;
import com.gao.util.ResultVO;
import com.xiaoleilu.hutool.date.DateUtil;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.gao.exception.OrderException.ErrorCode.*;

/**
 * @author gao
 * @time 2022/11/25 11:27:43
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
    @NonNull
    private OrderMapper orderMapper;
    @NonNull
    private OrderItemMapper orderItemMapper;
    @NonNull
    private ProductService productService;
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void addOrder(Integer productId, Integer quantity) {
        addOrder(Arrays.asList(productId), Arrays.asList(quantity));
    }

    @Override
    public void addOrder(List<Integer> productIds, List<Integer> quantities) {
        Order order = new Order();
        // 为了测试方便，模拟1号用户下的订单
        order.setUserId(1);
        order.setOrderDate(DateUtil.date());
        orderMapper.insert(order);

        double orderTotalPrice = addOrderItemsToOrder(order.getOrderId(), productIds, quantities);
        order.setTotalPrice(orderTotalPrice);

        orderMapper.updateByPrimaryKey(order);
    }

    private double addOrderItemsToOrder(Integer orderId, List<Integer> productIds, List<Integer> quantities) {
        double orderTotalPrice = 0;
        for (int i = 0; i < productIds.size(); i++) {
            int productId = productIds.get(i);
            int quantity = quantities.get(i);
            orderTotalPrice += addOrderItemToOrder(orderId, productId, quantity);
            deductProductStock(productId, quantity);
        }
        return orderTotalPrice;
    }

    private double addOrderItemToOrder(Integer orderId, Integer productId, Integer quantity) {
        Product product = findProduct(productId);
        OrderItem orderItem = new OrderItem();
        orderItem.setProductId(product.getProductId());
        orderItem.setQuantity(quantity);
        orderItem.setOrderId(orderId);
        orderItemMapper.insert(orderItem);
        return product.getPrice() * quantity;
    }

    private Product findProduct(Integer productId) {
        ResultVO resultVO = findRemoteProduct(productId);
        Product product = transferMapToProduct((Map) resultVO.getData());
        return product;
    }

    private ResultVO findRemoteProduct(Integer productId) {
        ResultVO resultVO = productService.find(productId);
        Integer code = resultVO.getCode();
        if (code.equals(ResultCode.FAILURE.getCode())) {
            throw new OrderException(REMOTE_PRODUCT_FOUND_FAILURE, productId);
        }
        return resultVO;
    }

    private Product transferMapToProduct(Map map) {
        Product product = null;
        try {
            String json = objectMapper.writeValueAsString(map);
            product = objectMapper.readValue(json, Product.class);
        } catch (JsonProcessingException e) {
            throw new OrderException(e);
        }
        return product;
    }

    private void deductProductStock(Integer productId, Integer quantity) {
        deductRemoteProductStock(productId, quantity);
    }

    private void deductRemoteProductStock(Integer productId, Integer quantity) {
        ResultVO resultVO = productService.deductStock(productId, quantity);
        if (resultVO.getCode().equals(ResultCode.FAILURE.getCode())) {
            throw new OrderException(REMOTE_PRODUCT_DEDUCT_STOCK_FAILURE, productId);
        }
    }

    @Override
    @SentinelResource("getOrderDetailByOrderId")
    public Order getOrderDetailByOrderId(Integer orderId) {
        Order order = orderMapper.selectByPrimaryKey(orderId);
        if (order == null) {
            throw new OrderException(ORDER_NOT_FOUND, orderId);
        }
        List<OrderItem> orderItems = getOrderItems(orderId);
        order.setOrderItems(new LinkedHashSet<>(orderItems));
        setProductToOrderItem(orderItems);
        return order;
    }


    private List<OrderItem> getOrderItems(Integer orderId) {
        OrderItemExample orderItemExample = new OrderItemExample();
        orderItemExample.or().andOrderIdEqualTo(orderId);
        List<OrderItem> orderItems = orderItemMapper.selectByExample(orderItemExample);
        return orderItems;
    }

    private void setProductToOrderItem(List<OrderItem> orderItems) {
        Map<Integer, OrderItem> productIdToOrderItem = makeProductIdMapToOrderItem(orderItems);
        Set<Integer> productIds = productIdToOrderItem.keySet();
        List<Product> products = getProductsByProductIds(productIds);

        products.forEach(product -> {
            try {
                productIdToOrderItem.get(product.getProductId()).setProduct(product);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    private Map<Integer, OrderItem> makeProductIdMapToOrderItem(List<OrderItem> orderItems) {
        Map<Integer, OrderItem> productIdToOrderItem = orderItems.stream().collect(Collectors.toMap(
                orderItem -> orderItem.getProductId(),
                orderItem -> orderItem
        ));
        return productIdToOrderItem;
    }

    private List<Product> getProductsByProductIds(Set<Integer> productIds) {
        ResultVO resultVO = productService.findByProductIds(new ArrayList(productIds));
        List<Map<String, Object>> data = (List<Map<String, Object>>) resultVO.getData();
        List<Product> products = data.stream().map(map -> {
            try {
                String json = objectMapper.writeValueAsString(map);
                Product product = objectMapper.readValue(json, Product.class);
                return product;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        return products;
    }
}
